package com.moxi.mogublog.admin.restapi;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.moxi.mogublog.admin.global.MessageConf;
import com.moxi.mogublog.admin.global.SQLConf;
import com.moxi.mogublog.admin.global.SysConf;
import com.moxi.mogublog.commons.entity.Admin;
import com.moxi.mogublog.commons.entity.CategoryMenu;
import com.moxi.mogublog.commons.entity.Role;
import com.moxi.mogublog.commons.feign.PictureFeignClient;
import com.moxi.mogublog.commons.feign.RedisFeignClient;
import com.moxi.mogublog.config.jwt.JwtTokenUtil;
import com.moxi.mogublog.config.jwt.properties.TokenProperties;
import com.moxi.mogublog.constants.CommonConstant;
import com.moxi.mogublog.redis.prefix.common.LoginKey;
import com.moxi.mogublog.utils.CheckUtils;
import com.moxi.mogublog.utils.IpUtils;
import com.moxi.mogublog.utils.ResultUtil;
import com.moxi.mogublog.utils.StringUtils;
import com.moxi.mogublog.xo.service.AdminService;
import com.moxi.mogublog.xo.service.CategoryMenuService;
import com.moxi.mogublog.xo.service.RoleService;
import com.moxi.mogublog.xo.utils.WebUtil;
import com.moxi.mougblog.base.enums.EMenuType;
import com.moxi.mougblog.base.result.ResultVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * <p>
 * 登录管理RestApi(为了更好地使用security放行把登录管理放在AuthRestApi中)
 * </p>
 *
 * @author JackLi
 * @date 2020-05-27
 */
@RestController
@RequestMapping("/auth")
@Api(value = "登录管理相关接口", tags = {"登录管理相关接口"})
@Slf4j
public class LoginRestApi {

    @Autowired
    private TokenProperties tokenProperties;

    @Autowired
    private WebUtil webUtil;

    @Autowired
    private AdminService adminService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private CategoryMenuService categoryMenuService;

    @Autowired
    private RedisFeignClient redisFeignClient;

    @Autowired
    private PictureFeignClient pictureFeignClient;

    /**
     * [后台管理系统] 用户登录接口
     */
    @ApiOperation(value = "用户登录", notes = "用户登录")
    @PostMapping("/login")
    public String login(
            HttpServletRequest request,
            @ApiParam(name = "username", value = "用户名或邮箱或手机号", required = false) @RequestParam(name = "username", required = false) String username,
            @ApiParam(name = "password", value = "密码") @RequestParam(name = "password", required = false) String password,
            @ApiParam(name = "isRememberMe", value = "是否记住账号密码") @RequestParam(name = "isRememberMe", required = false, defaultValue = "0") int isRememberMe) {

        // 入参校验：账号或密码不能为空
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            return ResultUtil.result(SysConf.ERROR, "账号或密码不能为空");
        }

        // 查询密码错误次数，确定是否锁定账号登录  TODO 需要思考一下，使用ip作为校验密码错误的次数，会不会有影响，比如说在A地登录4次错误，换到B地登录则次数重计
        String ip = IpUtils.getIpAddr(request);
        ResultVO<String> ret = redisFeignClient.get(LoginKey.LOGIN_LIMIT.getPrefix() + ip);
        String limitCountStr = ret.getData();
        if (StringUtils.isNotEmpty(limitCountStr)) {
            Integer limitCount = Integer.valueOf(limitCountStr);
            if (limitCount >= CommonConstant.NUM_FIVE) {
                return ResultUtil.result(SysConf.ERROR, "密码输错次数过多,已被锁定30分钟");
            }
        }

        // 校验用户名的类型：邮箱 / 手机号
        boolean isEmail = CheckUtils.checkEmail(username);
        boolean isMobile = CheckUtils.checkMobileNumber(username);

        // TODO （MyBatis）QueryWrapper的学习
        QueryWrapper<Admin> queryWrapper = new QueryWrapper<>();
        if (isEmail) {
            queryWrapper.eq(SQLConf.EMAIL, username);
        } else if (isMobile) {
            queryWrapper.eq(SQLConf.MOBILE, username);
        } else {
            queryWrapper.eq(SQLConf.USER_NAME, username);
        }
        Admin admin = adminService.getOne(queryWrapper);
        if (admin == null) {
            // TODO 根据输入的账号，未查询到用户信息，应提示如下：账号信息不存在（注：这里有个漏洞，容易被人刷账号）
            return ResultUtil.result(SysConf.ERROR, String.format(MessageConf.LOGIN_ERROR, setLoginCommit(request)));
        }
        // 验证密码
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        boolean isPassword = encoder.matches(password, admin.getPassWord());
        if (!isPassword) {
            // 密码错误，返回提示
            return ResultUtil.result(SysConf.ERROR, String.format(MessageConf.LOGIN_ERROR, setLoginCommit(request)));
        }

        List<String> roleUids = new ArrayList<>(CommonConstant.NUM_ONE);
        roleUids.add(admin.getRoleUid());
        List<Role> roleList = (List<Role>) roleService.listByIds(roleUids);
        if (roleList.size() <= 0) {
            return ResultUtil.result(SysConf.ERROR, MessageConf.NO_ROLE);
        }
        String roleNames = "";
        for (Role role : roleList) {
            roleNames += (role.getRoleName() + CommonConstant.SYMBOL_COMMA);
        }
        String roleName = roleNames.substring(0, roleNames.length() - 2);
        // 记住登录的时长 TODO 本后台管理暂无设置，默认值为0，即不记住登录
//        long expiresSecond = isRememberMe == 1 ? tokenProperties.getIsRememberMeExpiresSecond() : tokenProperties.getAudience().getExpiresSecond();
        if (isRememberMe == 1) {
            tokenProperties.getAudience().setExpiresSecond(tokenProperties.getIsRememberMeExpiresSecond());
        }
        String jwtToken = JwtTokenUtil.createJWT(
                admin.getUid(),
                admin.getUserName(),
                roleName,
                tokenProperties.getAudience());

        String token = tokenProperties.getHead() + jwtToken;
        Map<String, Object> result = new HashMap<>(CommonConstant.NUM_ONE);
        result.put(SysConf.TOKEN, token);

        /**
         * 进行登录相关操作
         */
        Integer count = admin.getLoginCount() + 1;
        admin.setLoginCount(count);
        admin.setLastLoginIp(ip);
        admin.setLastLoginTime(new Date());
        // TODO 这个方法巧：直接通过id进行更新
        admin.updateById();
        return ResultUtil.result(SysConf.SUCCESS, result);
    }

    /**
     * 获取用户信息 [登录成功后]
     * <p/>
     * TODO token 信息可以放置在 header 中
     *
     * @param request
     * @return
     */
    @ApiOperation(value = "用户信息", notes = "用户信息", response = String.class)
    @GetMapping(value = "/info")
    public String info(
            HttpServletRequest request
//            , @ApiParam(name = "token", value = "token令牌", required = false) @RequestParam(name = "token", required = false) String token
    ) {

        Map<String, Object> map = new HashMap<>(CommonConstant.NUM_THREE);

        Object adminUidAttrObj = request.getAttribute(SysConf.ADMIN_UID);
        if (adminUidAttrObj == null) {
            return ResultUtil.result(SysConf.ERROR, "token用户过期");
        }
        Admin admin = adminService.getById(adminUidAttrObj.toString());

        // 获取 header 中的 token 信息
        String token = request.getHeader("Authorization");
        map.put("token", token);

        // 获取图片
        if (StringUtils.isNotEmpty(admin.getAvatar())) {
            String pictureList = pictureFeignClient.getPicture(admin.getAvatar(), SysConf.FILE_SEGMENTATION);
            List<String> PictureList = webUtil.getPicture(pictureList);
            admin.setPhotoList(PictureList);
            if (PictureList.size() > 0) {
                map.put(SysConf.AVATAR, PictureList.get(0));
            } else {
                map.put(SysConf.AVATAR, "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif");
            }
        }
        // 加载这些角色所能访问的菜单页面列表
        // 1)获取该管理员所有角色
        List<String> roleUid = new ArrayList<>(1);
        roleUid.add(admin.getRoleUid());
        Collection<Role> roleList = roleService.listByIds(roleUid);
        map.put(SysConf.ROLES, roleList);
        return ResultUtil.result(SysConf.SUCCESS, map);
    }

    /**
     * 获取当前用户的菜单
     *
     * @param request
     * @return
     */
    @ApiOperation(value = "获取当前用户的菜单", notes = "获取当前用户的菜单", response = String.class)
    @GetMapping(value = "/getMenu")
    public String getMenu(HttpServletRequest request) {
        Object adminUidAttrObj = request.getAttribute(SysConf.ADMIN_UID);
        if (adminUidAttrObj == null) {
            return ResultUtil.result(SysConf.ERROR, "token用户过期");
        }
        Admin admin = adminService.getById(adminUidAttrObj.toString());

        // 加载这些角色所能访问的菜单页面列表
        //1)获取该管理员所有角色
        List<String> roleUid = new ArrayList<>(1);
        roleUid.add(admin.getRoleUid());
        Collection<Role> roleList = roleService.listByIds(roleUid);

        List<String> categoryMenuUidList = new ArrayList<>(roleList.size());

        roleList.forEach(item -> {
            String categoryMenuUids = item.getCategoryMenuUids();
            String[] uids = categoryMenuUids.replace("[", "").replace("]", "").replace("\"", "").split(",");
            for (int a = 0; a < uids.length; a++) {
                categoryMenuUidList.add(uids[a]);
            }
        });

        Collection<CategoryMenu> categoryMenuList = categoryMenuService.listByIds(categoryMenuUidList);
        // 从三级分类中查询出二级分类
        List<CategoryMenu> buttonList = new ArrayList<>(categoryMenuList.size());
        Set<String> secondMenuUidList = new HashSet<>(categoryMenuList.size());
        categoryMenuList.forEach(item -> {
            // 查询二级分类
            if (item.getMenuType() == EMenuType.MENU && item.getMenuLevel() == SysConf.NUM_TWO) {
                secondMenuUidList.add(item.getUid());
            }
            // 从三级分类中，得到二级分类
            if (item.getMenuType() == EMenuType.BUTTON && StringUtils.isNotEmpty(item.getParentUid())) {
                // 找出二级菜单
                secondMenuUidList.add(item.getParentUid());
                // 找出全部按钮
                buttonList.add(item);
            }
        });

        Collection<CategoryMenu> childCategoryMenuList = new ArrayList<>();
        List<String> parentCategoryMenuUids = new ArrayList<>();
        if (secondMenuUidList.size() > 0) {
            childCategoryMenuList = categoryMenuService.listByIds(secondMenuUidList);
        }
        childCategoryMenuList.forEach(item -> {
            //选出所有的二级分类
            if (item.getMenuLevel() == SysConf.NUM_TWO) {
                if (StringUtils.isNotEmpty(item.getParentUid())) {
                    parentCategoryMenuUids.add(item.getParentUid());
                }
            }
        });

        Collection<CategoryMenu> parentCategoryMenuList = new ArrayList<>();
        if (parentCategoryMenuUids.size() > 0) {
            parentCategoryMenuList = categoryMenuService.listByIds(parentCategoryMenuUids);
        }
        List<CategoryMenu> list = new ArrayList<>(parentCategoryMenuList);

        // 对菜单的 parent 进行排序
        Collections.sort(list);

        Map<String, Object> map = new HashMap<>(3);
        map.put(SysConf.PARENT_LIST, list);
        map.put(SysConf.SON_LIST, childCategoryMenuList);
        map.put(SysConf.BUTTON_LIST, buttonList);
        return ResultUtil.result(SysConf.SUCCESS, map);
    }

    @ApiOperation(value = "退出登录", notes = "退出登录", response = String.class)
    @PostMapping(value = "/logout")
    public String logout(@ApiParam(name = "token", value = "token令牌", required = false) @RequestParam(name = "token", required = false) String token) {
        String destroyToken = null;
        return ResultUtil.result(SysConf.SUCCESS, destroyToken);
    }

    /**
     * 设置登录限制，返回剩余次数
     * 密码错误5次，将会锁定10分钟
     *
     * @param request
     */
    private Integer setLoginCommit(HttpServletRequest request) {
        String ip = IpUtils.getIpAddr(request);
        ResultVO<String> ret = redisFeignClient.get(LoginKey.LOGIN_LIMIT.getPrefix() + ip);
        String count = ret.getData();
        Integer surplusCount = 5;
        if (StringUtils.isNotEmpty(count)) {
            Integer countTemp = Integer.valueOf(count) + 1;
            surplusCount = surplusCount - countTemp;
            redisFeignClient.setEx(LoginKey.LOGIN_LIMIT.getPrefix() + ip, String.valueOf(countTemp), 30 * 60);
        } else {
            surplusCount = surplusCount - 1;
            redisFeignClient.setEx(LoginKey.LOGIN_LIMIT.getPrefix() + ip, "1", 30 * 60);
        }
        return surplusCount;
    }

}
